package utils

import (
	"reflect"
	"sync"

	"gitee.com/xungen/goweb/errno"
)

var fieldmap = sync.Map{}

func Copy(dest, src any) {
	s := reflect.ValueOf(src)
	if s.Kind() == reflect.Ptr {
		if s.IsNil() {
			return
		}
		s = s.Elem()
	}

	d := reflect.ValueOf(dest)
	if d.Kind() == reflect.Ptr {
		d = d.Elem()
	}

	if clazz := d.Type(); isValue(clazz) {
		copyValue(d, s)
	} else if num := clazz.NumField(); num > 0 {
		fields := GetFieldIndexMap(s.Type())
		if len(fields) == 0 {
			return
		}

		for i := 0; i < num; i++ {
			if idx, ok := fields[clazz.Field(i).Name]; ok {
				sv := s.Field(idx)
				if sv.Kind() == reflect.Ptr {
					if sv.IsNil() {
						continue
					}
					sv = sv.Elem()
				}

				if dv := d.Field(i); isValue(dv.Type()) {
					copyValue(dv, sv)
				} else if dv.Kind() == reflect.Slice {
					copySlice(dv, sv)
				} else {
					copyStruct(dv, sv)
				}
			}
		}
	}
}

func GetFieldIndexMap(clazz reflect.Type) map[string]int {
	if data, ok := fieldmap.Load(clazz); ok {
		return data.(map[string]int)
	} else if clazz.Kind() == reflect.Struct {
		num := clazz.NumField()
		res := make(map[string]int, clazz.NumField())
		for i := 0; i < num; i++ {
			res[clazz.Field(i).Name] = i
		}
		fieldmap.Store(clazz, res)
		return res
	}
	return nil
}

func isValue(clazz reflect.Type) bool {
	if kind := clazz.Kind(); kind == reflect.Ptr {
		return IsBaseValue(clazz.Elem().Kind())
	} else {
		return IsBaseValue(kind)
	}
}

func copyValue(dest, src reflect.Value) {
	st := src.Type()
	sk := st.Kind()
	if sk == reflect.Ptr {
		if src.IsNil() {
			return
		}
		st = st.Elem()
		sk = st.Kind()
		src = src.Elem()
	}

	dt := dest.Type()
	dk := dt.Kind()
	if dk == reflect.Ptr {
		dt = dt.Elem()
		dk = dt.Kind()
		if dest.IsNil() {
			if src.IsZero() {
				return
			}
			dest.Set(reflect.New(dt))
		}
		dest = dest.Elem()
	}

	if dk == sk {
		dest.Set(src)
		return
	}

	switch dk {
	case sk:
		dest.Set(src)
	case reflect.String:
		dest.SetString(ToString(src))
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		dest.SetInt(errno.AssertNoError(ToInteger[int64](src)))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		dest.SetUint(errno.AssertNoError(ToInteger[uint64](src)))
	case reflect.Float32, reflect.Float64:
		dest.SetFloat(errno.AssertNoError(ToFloat(src)))
	case reflect.Bool:
		dest.SetBool(errno.AssertNoError(ToInteger[int](src)) > 0)
	}
}

func copySlice(dest, src reflect.Value) {
	num := src.Len()
	typ := dest.Type()
	arr := reflect.MakeSlice(typ, 0, num)
	if typ = typ.Elem(); typ.Kind() == reflect.Ptr {
		for i := 0; i < num; i++ {
			item := reflect.New(typ.Elem())
			Copy(item.Interface(), src.Index(i).Interface())
			arr = reflect.Append(arr, item)
		}
	} else {
		for i := 0; i < num; i++ {
			item := reflect.New(typ)
			Copy(item.Interface(), src.Index(i).Interface())
			arr = reflect.Append(arr, item.Elem())
		}
	}
	dest.Set(arr)
}

func copyStruct(dest, src reflect.Value) {
	typ := dest.Type()
	kind := dest.Kind()
	switch kind {
	case reflect.Struct:
		val := reflect.New(typ)
		Copy(val.Interface(), src.Interface())
		dest.Set(val.Elem())
	case reflect.Ptr:
		if typ = typ.Elem(); typ.Kind() == reflect.Struct {
			val := reflect.New(typ)
			Copy(val.Interface(), src.Interface())
			dest.Set(val)
		}
	}
}
